
#ifndef PEERTUBE_HTP
#define PEERTUBE_HPP

#include <string>
#include <cstring>
#include <iostream>
#include <sstream>


int interpret(const std::string &user_input, std::string &host, std::string &id) {
    // what did the user link?
    char *token = new char[user_input.length() + 1];
    strcpy(token, user_input.c_str());
    token = strtok(token, "/");
    unsigned int state = 0;
    bool is_playlist = false;
    while (token != NULL) {
        switch (state) {
            case 0: // protocol
                if (strcmp(token, "https:") != 0 
                 && strcmp(token, "http:") != 0) {
                    goto endwhile;
                }
                state++;
                break;
            case 1:  // domain
                host = std::string(token);
                state++;
                break;
            case 2: // can be either api or 'w'
                if (strcmp(token, "w") == 0) {
                    state++;
                } else if (strcmp(token, "api") == 0) {
                    state = 'a';
                } break;
            case 3: // can either be playlist indicator or a video id
                if (strcmp(token, "p") == 0) {
                    state++;
                } else {
                    id = std::string(token);
                } break;
            case 4: // playlist id
                id = std::string(token);
                break;
            case 'a': // api version indicator
                state++;
                break;
            case 'b':
                if (strcmp(token, "videos") == 0) {
                    state++;
                } else if (strcmp(token,"video-playlists") == 0) {
                    is_playlist = true;
                    state++;
                } break;
            case 'c':
                id = std::string(token);
                break;
        }
        token = strtok(NULL, "/");
    }
    endwhile:
    delete [] token;
    if (is_playlist) {
        state++; // so caller func knows api video vs api playlist
    }
    return state;
}

// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo
// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideoPlaylistVideos
std::string get_endpoint(const std::string &user_input, bool &is_playlist) {
    std::string host;
    std::string id;
    int what = 0;
    what = interpret(user_input, host, id);
    std::string url = std::string("");
    std::string api;
    std::stringstream ss;
    switch (what) {
        default: 
            std::cout << what << std::endl;
        case 0:
        case 1:
            std::cout << "URL not recognized." << std::endl;
            break;
        case 3:
            std::cout << "video page" << std::endl;
            api = "/api/v1/videos/"; //(host, id)
            ss << "https://" << host << api << id;
            url = ss.str();
            break;
        case 'c': // video api
            std::cout << "video api endpoint" << std::endl;
            url = user_input;
            break;
        case 'd':
            std::cout << "playlist api endpoint" << std::endl;
            url = user_input;
            break;
        case 4:
            std::cout << "playlist page" << std::endl;
            api = "/api/v1/video-playlists/";
            ss << "https://" << host << api << id << "/videos";
            url = ss.str();
            break;
    }
    return url;
}

// #include "peertube/model/video.h" <- model does not compile
// https://docs.joinpeertube.org/api-rest-reference.html#operation/getVideo
// salvage the stuff that didn't compile.'

#define FILE_T_STR_COUNT 6
enum {
    MAGNET_URI,
    TORRENT_URL,
    TORRENTDOWNLOAD_URL,
    FILE_URL,
    FILEDOWNNLOAD_URL,
    METADATA_URL
} file_t_attribs;

struct file_t {
//     char *magnetUri;
    int resolution; // ignore label
    int size;
//     char *torrentUrl;
//     char *torrentDownloadUrl;
//     char *fileUrl;
//     char *fileDownloadUrl;
    int fps;
//     char *metadataurl;
    char *attribs[FILE_T_STR_COUNT]; // B)
};

struct file_t* file_init (int resolution,
                          int size,
                          int fps,
                          unsigned int len_data,
                          const char **data) {
    struct file_t *f = (file_t*)malloc(sizeof(file_t));
    f -> resolution = resolution;
    f -> size = size;
    f -> fps = fps;
    if (len_data != FILE_T_STR_COUNT) {
        return NULL;
    }
    // not going to take any cues 
    // on how to initialize a struct from openapigenerator
    unsigned int len;
    unsigned int i = 0;
    for (i; i < FILE_T_STR_COUNT; ++i) {
        if (data[i] == NULL) {
            return NULL;
        }
        len = strlen(data[i]) * sizeof(char);
        f -> attribs[i] = (char*)malloc(len);
        strcpy(f -> attribs[i], data[i]);
    }
    return f;
}

void file_print(const struct file_t * const f) {
    unsigned int i = 0;
    for (i; i < FILE_T_STR_COUNT; ++i) {
        fprintf(stdout, "%s\n", f -> attribs[i]);
    }
}

void file_free(struct file_t* f) {
    unsigned int i = 0;
    for (i; i < FILE_T_STR_COUNT; ++i) {
        free(f -> attribs[i]);
    }
    free(f);
}

struct video_t {
    int id;
    char *uuid;
    char *short_uuid;
//     int is_live;
//     char *created_at; //date time
//     char *published_at; //date time
//     char *updated_at; //date time
//     char *originally_published_at; //date time
    // skip other models
    char *description; // string
    int duration; //numeric
//     int is_local; //boolean
    char *name; // string
    int views; //numeric
    int likes; //numeric
    int dislikes; //numeric
    int nsfw; //boolean
    // skip other models again
    int file_count;
    struct file_t** files;
};

struct video_t *video_init(int id, 
              const char *uuid, 
              const char *short_uuid, 
              const char *description,
              int duration,
              const char *name,
              int views,
              int likes,
              int dislikes,
              int nsfw,
              int file_count,
              struct file_t** files) {

    struct video_t *v = (video_t*)malloc(sizeof(video_t));
    if (!v) {
        return NULL;
    }
    v -> id = id;
    v -> duration = duration;
    v -> views = views;
    v -> likes = likes;
    v -> dislikes = dislikes;
    v -> nsfw = nsfw;
    unsigned int len;
    if (uuid != NULL) {
        len = strlen(uuid) * sizeof(char);
        v -> uuid = (char *)malloc(len);
        strcpy(v -> uuid, uuid);
    }
    if (short_uuid != NULL) {
        len = strlen(short_uuid) * sizeof(char);
        v -> short_uuid = (char*)malloc(len);
        strcpy(v -> short_uuid, short_uuid);
    }
    if (description != NULL) {
        len = strlen(description) * sizeof(char);
        v -> description = (char*)malloc(len);
        strcpy(v -> description, description);
    }
    if (name != NULL) {
        len = strlen(name) * sizeof(char);
        v -> name = (char*)malloc(len);
        strcpy(v -> name, name);
    }
    v -> file_count = file_count;
    if (files != NULL) {
        v -> files = files;
    }
    return v;
}

#include <cstdio>
void video_free(struct video_t *v) {
    if (v == NULL) {
        return;
    }
    if (v -> uuid != NULL) {
        free(v -> uuid);
    }
    if (v -> short_uuid != NULL) {
        free(v -> short_uuid);
    }
    if (v -> description != NULL) {
        free(v -> description);
    }
    if (v -> name != NULL) {
        free(v -> name);
    }
    unsigned int i;
    for (i = 0; i < v-> file_count; ++i) {
        file_free(v -> files[i]);
    }
    free(v -> files);
    free(v);
}

void video_print(const struct video_t * const v) {
    if (v == NULL) {
        return;
    }
    fprintf(stdout, "%s\n", v -> name);
    fprintf(stdout, "%s\n", v -> description);
    fprintf(stdout, "%i views\n%i:%i (likes:dislikes)\n", 
            v -> views, v -> likes, v -> dislikes);
}

#include <cmath>
#include <climits>
// pick file download of a target resolution
struct file_t *video_pick_file(const struct video_t* const v, int target_res=0) {
    if (v == NULL) {
        return NULL;
    }
    if (v -> files == NULL) {
        return NULL;
    }
    if (target_res == 0) {
        // default behavior => pick best quality
        return v-> files[0];
    }
    int lowest_delta = INT_MAX;
    struct file_t *best_file = NULL;
    unsigned int i;
    for (i = 0; i < v -> file_count; ++i) {
        int res = v -> files[i] -> resolution;
        int delta = std::abs(target_res - res);
        if (delta < lowest_delta) {
            lowest_delta = delta;
            best_file = v -> files[i];
        }
    }
    return best_file;
}

#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>

 rapidjson::Value& get_video_files(rapidjson::Document& root) {
    rapidjson::Document *ptr = &root;
    rapidjson::Value& out = root["files"];
    assert(out.IsArray());
    if (out.Size() > 0) {
        return out;
    }
    // if the list is empty - HLS is enabled on the server
    // therefore, look under streamingPlaylists
    rapidjson::Value &playlists = root["streamingPlaylists"];
    assert(playlists.IsArray());
    for (rapidjson::SizeType i = 0; i < playlists.Size(); ++i) {
        if (!playlists[i]["type"].IsInt()) {
            continue;
        }
        // don't know what type other than 1 is.'
        if (playlists[i]["type"].GetInt() == 1) {
            out = playlists[i]["files"];
            break;
        } else {
            std::cout << "Found streamingPlaylist type != 1" << std::endl;
        }
    }
    return out;
}

struct file_t *init_from_json(const rapidjson::Value& file) {
    const char *elem_ints[] = {
//         "resolution", // id is nested inside resolution
        "size",
        "fps"
    };
    int ints[2];
    // keep order -see enum above struct file_t definition
    const char *elem_strs[FILE_T_STR_COUNT] = {
        "magnetUri",
        "torrentUrl",
        "torrentDownloadUrl",
        "fileUrl",
        "fileDownloadUrl",
        "metadataUrl"
    };
    const char *strs[FILE_T_STR_COUNT];
    int resolution;
    if (!file["resolution"]["id"].IsInt()) {
        return NULL;
    }
    resolution = file["resolution"]["id"].GetInt();
    unsigned int i = 0;
    for (i; i < 2; ++i) {
        const char *element = elem_ints[i];
        const rapidjson::Value &v = file[element];
        if (!v.IsInt()) {
            return NULL;
        }
        ints[i] = v.GetInt();
    }
    i = 0;
    for (i; i < FILE_T_STR_COUNT; ++i) {
        const char *element = elem_strs[i];
        const rapidjson::Value &v = file[element];
        if (!v.IsString()) {
            return NULL;
        }
        strs[i] = v.GetString();
    }
    struct file_t *foo = file_init(
        resolution, ints[0], ints[1], FILE_T_STR_COUNT, strs); 
    return foo;
}

struct video_t *init_from_json(rapidjson::Document& root) {
    #define INT_ATTRIB_COUNT 5
    const char *elem_int[INT_ATTRIB_COUNT] = {
        "id",
        "duration",
        "views",
        "likes",
        "dislikes",
    };
    int ints[INT_ATTRIB_COUNT];
    #define STR_ATTRIB_COUNT 4
    const char *elem_str[STR_ATTRIB_COUNT] = {
        "uuid",
        "shortUUID",
        "description",
        "name"
    };
    std::string strs[STR_ATTRIB_COUNT];

    unsigned int i;
    for (i = 0; i < INT_ATTRIB_COUNT; ++i) {
        const char *element = elem_int[i];
        rapidjson::Value& v = root[element];
        if (!v.IsInt()) {
            return NULL;
        }
        ints[i] = v.GetInt();
    }

    for (i = 0; i < STR_ATTRIB_COUNT; ++i) {
        const char *element = elem_str[i];
        rapidjson::Value& v = root[element];
        if (!v.IsString()) {
            return NULL;
        }
        strs[i] = v.GetString();
    }
    rapidjson::Value &v = root["nsfw"];
    if (!v.IsBool()) {
        return NULL;
    }
    bool is_nsfw = v.GetBool();
    int nsfw = (is_nsfw) ? 1 : 0;

    const rapidjson::Value &vf = get_video_files(root);
    unsigned int file_count = vf.Size();
    unsigned int size = file_count * sizeof(struct file_t**);

    struct file_t ** f = (struct file_t**)malloc(size);
    for (rapidjson::SizeType i = 0; i < vf.Size(); ++i) {
        f[i] = init_from_json(vf[i]);
    }
    struct video_t *bar = video_init(
        ints[0],
        strs[0].c_str(),
        strs[1].c_str(),
        strs[2].c_str(),
        ints[1],
        strs[3].c_str(),
        ints[2],
        ints[3],
        ints[4],
        nsfw,
        file_count,
        f
    );
    return bar;
}



std::string get_magnet_link(const rapidjson::Value& files) {
    unsigned int highest = 0;
    std::string best_video;
    // peertube tends to order highest to lowest, but just to make sure...
    for (rapidjson::SizeType i = 0; i < files.Size(); ++i ) {
        assert(files[i]["resolution"]["id"].IsInt());
        unsigned int resolution = files[i]["resolution"]["id"].GetInt();
        std::string magnet_link = files[i]["magnetUri"].GetString();
        if (resolution > highest) {
            highest = resolution;
            best_video = magnet_link;
        }
    }
    std::cout << "The best quality video is " << best_video << std::endl;
    return best_video;
}

#endif
